/**
 * developer: yeganaaa@163.com
 * time: 2018-10-04 17:37:05 GMT+6.0
 */

/**
 * Event Bus
 */
class EventBus {
  List<int> _idTable = [];
  Map<String, Map<int, Function>> _events = {};
  EventBus();

  static Map<String, EventBus> _cache = {};

  /**
   * Create an EventBus singleton instance
   */
  factory EventBus.singleton({String key = ""}){
    if (!_cache.containsKey(key)) _cache[key] = EventBus();
    return _cache[key];
  }

  /**
   * Subscribe an event handler,
   */
  int subscribe<DATA>(String addr, void handler(EventBus bus, DATA data)){
    var allocatedId = 0; //allocate an event id
    while (_idTable.any((id) => id == allocatedId)){
      allocatedId++;
    }
    var eventGroup = _events[addr];
    if (eventGroup == null){
      eventGroup = {};
      _events[addr] = eventGroup;
    }
    eventGroup[allocatedId] = handler;
    _idTable.add(allocatedId); //add event id to id_table.
    return allocatedId;
  }

  /**
   * Subscribe an event handler if not exists.
   */
  int subscribeIfNotExists<DATA>(String addr, void handler(EventBus bus, DATA data)){
    var allocatedId = -1;

    var eventGroup = _events[addr];
    if (eventGroup == null) {
      eventGroup = {};
      _events[addr] = eventGroup;
    }
    if (eventGroup.isEmpty){
      allocatedId = subscribe(addr, handler);
    }
    return allocatedId;
  }

  /**
   * UnSubscribe an event handler is exists.
   */
  bool unSubscribe(int eventId){
    var contains = false;
    _events.forEach((key, value) {
      if (contains) return;
      contains = value[eventId] != null;
      if (contains){
        value.remove(eventId);
        _idTable.remove(eventId);
      }
    });
    return contains;
  }

  /**
   * Subscribe an once event handler
   */
  void subscribeOnce<DATA>(String addr, void handler(EventBus bus, DATA data)){
    var eventId = subscribe(addr, handler);
    var trackerEventId = 0;
    trackerEventId = subscribe(addr, (bus, _){
      bus.unSubscribe(trackerEventId);
      bus.unSubscribe(eventId);
    });
  }

  /**
   * Publish an message to any handlers of this address
   */
  DATA publish<DATA>(String addr, DATA data){
    var eventGroup = _events[addr] ?? {};
    eventGroup.map((key, value) => MapEntry(key, value)).forEach((id, event){
      event(this, data);
    });
    return data;
  }

  /**
   * Unsubscribe all handlers of this address
   */
  void unSubscribeAll(String addr){
    var eventGroup = _events[addr] ?? {};
    eventGroup.map((key, value) => MapEntry(key, value)).forEach((id, event){
      _idTable.remove(id);
    });
    eventGroup.clear();
  }
}

